/**
 *
 */
package com.wedroid.r2d2.contact.handler;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;

import com.wedroid.entity.contact.Contacter;
import com.wedroid.entity.contact.ContacterProfile;
import com.wedroid.entity.contact.ContacterProfile.ContactProfileBuilder;
import com.wedroid.entity.contact.ContacterType;
import com.wedroid.entity.contact.Droid;
import com.wedroid.entity.contact.GroupMember;
import com.wedroid.r2d2.cache.PersistentAccessor;
import com.wedroid.r2d2.contact.Contact;
import com.wedroid.r2d2.resource.ResourceManager;
import com.wedroid.r2d2.service.ContactService;

/**
 * @author lute
 */
@Component("jdbcGroupMemberHandler")
public class JdbcGroupMemberHandler implements GroupMemberHandler {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final int MAX_THREAD_COUNT = 20;

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private ResourceManager resourceManager;

    @Autowired
    private ContactService contactService;

    @Autowired
    private PersistentAccessor accessor;

    /* (non-Javadoc)
     * @see com.wedroid.r2d2.contact.handler.GroupMemberHandler#handleGroupMembers(java.lang.String, java.util.Map)
     */
    public void handleGroupMembers(final String droidName, final Map<Contact, List<Contact>> groupMembers) {
        Droid droid = accessor.getDroid(droidName);
        if (droid != null) {
            Set<Contact> keySet = groupMembers.keySet();

            // Handle group synchronously
            Map<Contact, Contacter> groupContacters = new HashMap<>(keySet.size());
            for (Contact key : keySet) {
                Contacter group = handleGroup(droid, key);
                if (group != null) {
                    groupContacters.put(key, group);
                }
            }

            keySet = groupContacters.keySet();

            // Divide all group members into max task executor for not running out executors
            int totalTasks = keySet.size();
            int tasksPerThread = (totalTasks + MAX_THREAD_COUNT - 1) / MAX_THREAD_COUNT;
            int idleThreads = tasksPerThread * MAX_THREAD_COUNT - totalTasks;

            List<Contact> keys = new ArrayList<>(keySet);
            for (int i = 0; i < MAX_THREAD_COUNT; i++) {
                int fromIdx = i < idleThreads ?
                        (tasksPerThread - 1) * i :
                        (tasksPerThread - 1) * idleThreads + tasksPerThread * (i - idleThreads);
                int tasks = i < idleThreads ? tasksPerThread - 1 : tasksPerThread;

                if (tasks > 0) {
                    int toIdx = fromIdx + tasks;
                    List<Contact> subKeys = keys.subList(fromIdx, toIdx);

                    Map<Contact, Contacter> subGroupContacters = new HashMap<>(subKeys.size());
                    Map<Contact, List<Contact>> subMemberContacts = new HashMap<>(subKeys.size());
                    for (Contact key : subKeys) {
                        Contacter contacter = groupContacters.get(key);
                        subGroupContacters.put(key, contacter);

                        List<Contact> members = groupMembers.get(key);
                        subMemberContacts.put(key, members);
                    }

                    JdbcGroupMemberTask task = new JdbcGroupMemberTask(
                        Collections.unmodifiableList(subKeys),
                        groupContacters,
                        groupMembers
                    );
                    taskExecutor.execute(task);
                }
            }

        }
    }

    /**
     * @param droid
     * @param groupContact
     * @return
     */
    private Contacter handleGroup(final Droid droid, final Contact groupContact) {
        // Filter out empty name group, no reason to handle this type of group
        if (StringUtils.isBlank(groupContact.getNickName())) {
            logger.trace("#handleGroup - Group name is empty or null. No need to handle this group.");
            return null;
        }

        ContacterProfile profile = toContacterProfile(groupContact);

        // Download group head image
        String headImgUrl = resourceManager.requestAvatar(groupContact);
        profile.setHeadImgUrl(headImgUrl);

        Contacter group = new Contacter(profile, ContacterType.GROUP, droid);
        try {
            return contactService.createOrUpdateContacter(group);
        } catch (Exception e) {
            logger.error("#handleGroup - Creating or Updating group encounters exception: {}", e);
            return null;
        }
    }

    /**
     * @param contact
     * @return
     */
    private ContacterProfile toContacterProfile(final Contact contact) {
        return new ContactProfileBuilder().nickName(contact.getNickName())
                                          .remarkName(contact.getRemarkName())
                                          .displayName(contact.getDisplayName())
                                          .contactFlag(contact.getContactFlag())
                                          .memberCount(contact.getMemberCount())
                                          .alias(contact.getAlias())
                                          .sex(contact.getSex())
                                          .province(contact.getProvince())
                                          .city(contact.getCity())
                                          .signature(contact.getSignature())
                                          .build();
    }

    private class JdbcGroupMemberTask implements Runnable {

        private List<Contact> keys;

        private Map<Contact, Contacter> groups;

        private Map<Contact, List<Contact>> members;

        /**
         * @param keys
         * @param groups
         * @param members
         */
        public JdbcGroupMemberTask(List<Contact> keys, Map<Contact, Contacter> groups, Map<Contact, List<Contact>> members) {
            this.keys = keys;
            this.groups = groups;
            this.members = members;
        }

        /* (non-Javadoc)
         * @see java.lang.Runnable#run()
         */
        public void run() {
            for (Contact key : this.keys) {
                logger.trace("#run - Thread [{}] is gong to handle group members for group [{}]", Thread.currentThread().getName(), key.getNickName());
                Contacter group = this.groups.get(key);
                List<Contact> groupMembers = this.members.get(key);
                handleGroupMembersInternal(group, groupMembers);
            }
        }

        /**
         * @param droid
         * @param group
         * @param members
         */
        private void handleGroupMembersInternal(Contacter group, List<Contact> members) {
            for (Contact member : members) {
                ContacterProfile profile = toContacterProfile(member);
                GroupMember groupMember = new GroupMember(profile, group);
                try {
                    contactService.createOrUpdateGroupMember(groupMember);
                } catch (Exception e) {
                    logger.error("#handleGroupMembersInternal - Creating or updating group member [{}] encounters exception: {}", member.getNickName(), e);
                }
            }
        }
    }

}
